#include "nwudpserver.h"
#include <chrono>
#include <pthread.h>

namespace NetworkUtil {


UdpServer::UdpServer(const ConnectionConfigInfo& stConnCfgInfo)
    : BaseConnection(stConnCfgInfo)
{

}

UdpServer::~UdpServer()
{
    m_pfnRecvCallback = nullptr;
}

void UdpServer::setRecvCallbackFunc(std::function<void(const std::string& strDataBuf, const asio::ip::udp::endpoint& objSenderEndPoint)> pfnRecvCallback)
{
    m_pfnRecvCallback = pfnRecvCallback;
    return;
}

void UdpServer::init()
{
    if (m_bInited)
    {
        return;
    }

    try {
        if (!(m_sp4ObjIOCtx.get()))
        {
            m_sp4ObjIOCtx = std::make_shared<asio::io_context>();
        }

        if (!(m_sp4ObjSocket.get()))
        {
            m_sp4ObjSocket = std::make_shared<asio::ip::udp::socket>(*(m_sp4ObjIOCtx.get()), asio::ip::udp::endpoint(asio::ip::udp::v4(), m_stConnCfgInfo.ui16LocalPort));
        }

        m_sp4ObjIOCtx->run();

        m_bRunning = true;
        if (!(m_sp4thRecv.get()))
        {
            m_sp4thRecv = std::make_shared<std::thread> (&UdpServer::onRecvDataProcess, this);
        }

        m_bInited = true;
    } catch (std::exception& e) {
        printf("func [UdpServer::init], start server (%d) failed, error info: %s.\r\n" \
               , m_stConnCfgInfo.ui16LocalPort, e.what());

        m_bInited = true;
        deinit();
    }

    return;
}

void UdpServer::deinit()
{
    if (m_bInited)
    {
        m_bRunning = false;

        try {
            if (m_sp4ObjSocket.get())
            {
                m_sp4ObjSocket->shutdown(asio::ip::udp::socket::shutdown_both);
                m_sp4ObjSocket->release();
                m_sp4ObjSocket->close();
            }

            if (m_sp4ObjIOCtx.get())
            {
                m_sp4ObjIOCtx->stop();
            }

            if (m_sp4thRecv.get())
            {
                pthread_cancel(m_sp4thRecv->native_handle()); // 因为read会阻塞线程，所以需要强行杀死这个线程
                if (m_sp4thRecv->joinable()) {
                    m_sp4thRecv->join();
                }
            }

        } catch (...) {
            // catch some exception
        }

        if (m_sp4thRecv.get())
        {
            m_sp4thRecv.reset();
            m_sp4thRecv = nullptr;
        }

        if (m_sp4ObjSocket.get())
        {
            m_sp4ObjSocket.reset();
            m_sp4ObjSocket = nullptr;
        }

        if (m_sp4ObjIOCtx.get())
        {
            m_sp4ObjIOCtx.reset();
            m_sp4ObjIOCtx = nullptr;
        }

        m_bInited = false;
    }

    return;
}

uint32_t UdpServer::read(char* pDataBuf, uint32_t ui32DataBufLen, asio::ip::udp::endpoint& objSenderEndPoint)
{
    uint32_t ui32Ret = 0;
    if (m_sp4ObjSocket.get() && pDataBuf)
    {
        std::size_t s32Ret = m_sp4ObjSocket->receive_from(asio::buffer(pDataBuf, static_cast<std::size_t>(ui32DataBufLen))
                                                          , objSenderEndPoint);
        ui32Ret = static_cast<uint32_t>(s32Ret);
    }

    return ui32Ret;
}

uint32_t UdpServer::read(std::string& strDataBuf, asio::ip::udp::endpoint& objSenderEndPoint)
{
#if 0
    uint32_t ui32Ret = 0;
    if (m_sp4ObjSocket.get())
    {
        std::size_t s32Ret = m_sp4ObjSocket->receive_from(asio::buffer(strDataBuf), objSenderEndPoint);
        ui32Ret = static_cast<uint32_t>(s32Ret);
    }
    return ui32Ret;
#else
    char *pBuffer = new (std::nothrow) char[m_stConnCfgInfo.ui32BufMaxSize];
    if (!pBuffer)
    {
        return 0;
    }

    memset(pBuffer, 0, m_stConnCfgInfo.ui32BufMaxSize);
    uint32_t ui32Ret = read(pBuffer, m_stConnCfgInfo.ui32BufMaxSize, objSenderEndPoint);
    if (0 < ui32Ret)
    {
        strDataBuf = std::string(pBuffer, ui32Ret);
    }

    if (pBuffer)
    {
        delete[] pBuffer;
        pBuffer = nullptr;
    }
    return ui32Ret;
#endif
}

uint32_t UdpServer::read(std::string& strDataBuf)
{
    asio::ip::udp::endpoint objSenderEndPoint;
    return read(strDataBuf, objSenderEndPoint);
}

uint32_t UdpServer::write(const char* pDataBuf, uint32_t ui32DataBufLen, const asio::ip::udp::endpoint& objSenderEndPoint)
{
    uint32_t ui32Ret = 0;

    if (m_sp4ObjSocket.get() && pDataBuf)
    {
        if (m_stConnCfgInfo.bAsync)
        {
            std::error_code errCode;
            std::size_t sendByte;

            m_sp4ObjSocket->async_send_to(asio::buffer(pDataBuf, static_cast<std::size_t>(ui32DataBufLen))
                                          , objSenderEndPoint
                                          , [&errCode, &sendByte, this](std::error_code ec, std::size_t bytes_sent)
            {
                (void)this;
                errCode = ec;
                sendByte = bytes_sent;
                return;
            });
        }
        else
        {
            std::size_t s32Ret = m_sp4ObjSocket->send_to(asio::buffer(pDataBuf, static_cast<std::size_t>(ui32DataBufLen))
                                                         , objSenderEndPoint);

            ui32Ret = static_cast<uint32_t>(s32Ret);
        }
    }

    return ui32Ret;
}

uint32_t UdpServer::write(const std::string& strDataBuf, const asio::ip::udp::endpoint& objSenderEndPoint)
{
    return write(strDataBuf.data(), static_cast<uint32_t>(strDataBuf.size()), objSenderEndPoint);
}

uint32_t UdpServer::write(const std::string& strDataBuf, const std::string &strIPv4, uint16_t ui16Port)
{
    asio::ip::udp::endpoint objEndpoint(asio::ip::make_address(strIPv4), ui16Port);
    return write(strDataBuf, objEndpoint);
}

void UdpServer::onRecvDataProcess()
{
    while (m_bRunning)
    {
        std::string strDataInfo = "";
        asio::ip::udp::endpoint objSenderEndPoint;
        read(strDataInfo, objSenderEndPoint);

        if (!(strDataInfo.empty()))
        {
            if (m_pfnRecvCallback)
            {
                m_pfnRecvCallback(strDataInfo, objSenderEndPoint);
            }
        }

        std::this_thread::yield();
    }

    return;
}

} // namespace NetworkUtil
